/*
 *  Brno University of Technology
 *
 *  Copyright (C) 2009-2010 by Brno University of Technology and the contributors
 *
 *  Complete list of developers available at our web site:
 *
 *       http://spl.utko.feec.vutbr.cz
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package cz.vutbr.feec.utko.ef.evolution;

import java.awt.Toolkit;

import org.apache.log4j.Logger;

import cz.vutbr.feec.utko.ef.core.Config;
import cz.vutbr.feec.utko.ef.core.Population;
import cz.vutbr.feec.utko.ef.core.Random;
import cz.vutbr.feec.utko.ef.evolution.stats.DefaultCrossoverAndMutationRateManager;
import cz.vutbr.feec.utko.ef.individuals.CrossoverIsNotPossibleException;
import cz.vutbr.feec.utko.ef.individuals.MutationIsNotPossibleException;

/**
 * The Class DefaultEvolutionSpecifier defines the way how the population is
 * processed in each evolution. In this
 */
public abstract class DefaultEvolutionSpecifier extends
		EvolutionSpecifierAdapter {
	/** The evaluator. */
	private IFitnessEvaluator evaluator;

	/** The Constant log. */
	private static final Logger log = Logger
			.getLogger(DefaultEvolutionSpecifier.class);

	private double mutationSuccessRate = 0.5;

	private double crossoverSuccessRate = 0.5;

	/**
	 * Instantiates a new default evolution specifier.
	 * 
	 * @param eval
	 *            the evaluator of chromozomes
	 * @param cmrManager
	 *            the statistics logger
	 */
	public DefaultEvolutionSpecifier(Config cfg, IFitnessEvaluator eval,DefaultCrossoverAndMutationRateManager cmRateManager) {
		super(cmRateManager, cfg);
		this.evaluator = eval;
		if (eval == null) {
			throw new RuntimeException("Fitness evaluator cannot be null.");
		}
	}

	/* (non-Javadoc)
	 * @see cz.vutbr.feec.utko.ef.evolution.IEvolutionSpecifier#assignFitness(cz.vutbr.feec.utko.ef.evolution.Chromozome)
	 */
	@Override
	public void assignFitness(Chromozome ch) {
		ch.setCachedFitness(evaluator.computeFitnessValue(ch));
	}

	/**
	 * Creates the new chromozome.
	 * 
	 * @param config the config
	 * 
	 * @return the chromozome
	 */
	public abstract Chromozome createNewChromozome(Config config);

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * cz.vutbr.feec.utko.ef.evolution.EvolutionSpecifierAdapter#crossoverAll
	 * (cz.vutbr.feec.utko.ef.core.Config,
	 * cz.vutbr.feec.utko.ef.core.Population)
	 */
	@Override
	public Population crossoverAll(Config cfg, Population p) {
		log.debug("Beginning crossoverall with population size:" + p.size());
		Random rnd = new Random();
		Population res = new Population();
		int crossovers = 0;
		int failures = 0;
		int succFirst = 0;
		int succSecond = 0;
		for (int k = 0; k < p.getSize(); k++) {

			// Skip 50 % of mutation
			if (rnd.nextDouble() > cfg.getCrossoverRate()) {
				continue;
			}

			Chromozome first = p.getIndividual(k);
			int j = rnd.nextInt(p.getSize());
			Chromozome second = p.getIndividual(j);
			try {
				// first.validateTree();
				// second.validateTree();

				Chromozome firstClone = first.cloneDeep();
				Chromozome secondClone = second.cloneDeep();

				firstClone.crossover(secondClone);

				assert first.getCachedFitness() != null;
				assert second.getCachedFitness() != null;

				double prev1 = first.getCachedFitness();
				double prev2 = second.getCachedFitness();

				first.validate();
				second.validate();

				double maxFitness = Math.max(first.getCachedFitness(), second
						.getCachedFitness());

				// compute fitnesses
				firstClone.setCachedFitness(evaluator
						.computeFitnessValue(firstClone));
				secondClone.setCachedFitness(evaluator
						.computeFitnessValue(secondClone));

				if (firstClone.getCachedFitness() > first.getCachedFitness()
						&& firstClone.getCachedFitness() > maxFitness) {
					assert firstClone.getCachedFitness() != null;
					res.add(firstClone);
					log.debug("Crossovef of individuals with fitness: "
							+ first.getCachedFitness() + " and "
							+ second.getCachedFitness() + ", improv:"
							+ (firstClone.getCachedFitness() - prev1));
					succFirst++;
				}
				if (secondClone.getCachedFitness() > cfg.getMinimalFitness()
						&& secondClone.getCachedFitness() > maxFitness) {
					assert secondClone.getCachedFitness() != null;
					res.add(secondClone);
					log.debug("Crossovef of individuals with fitness: "
							+ first.getCachedFitness() + " and "
							+ second.getCachedFitness() + ", improv:"
							+ (secondClone.getCachedFitness() - prev2));
					succSecond++;
				}
				crossovers++;
			} catch (CrossoverIsNotPossibleException e) {
				failures++;
			}
		}
		if (crossovers > 0) {
			crossoverSuccessRate = (double) (succFirst + succSecond)
					/ (double) crossovers / 2.0;
		} else {
			crossoverSuccessRate = 0.5;
		}

		assert !Double.isNaN(crossoverSuccessRate);

		log.debug("CrossoverAll success:" + crossovers + ", failed:" + failures
				+ ", IMPROVED: " + crossoverSuccessRate);
		return res;
	}

	/* (non-Javadoc)
	 * @see cz.vutbr.feec.utko.ef.evolution.EvolutionSpecifierAdapter#getCrossoverSuccessRate()
	 */
	public double getCrossoverSuccessRate() {
		return crossoverSuccessRate;
	}

	/* (non-Javadoc)
	 * @see cz.vutbr.feec.utko.ef.evolution.EvolutionSpecifierAdapter#getMutationSuccessRate()
	 */
	public double getMutationSuccessRate() {
		return mutationSuccessRate;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * cz.vutbr.feec.utko.ef.evolution.IEvolutionSpecifier#getNumOfEvaluations()
	 */
	@Override
	public int getNumOfEvaluations() {
		return evaluator.getNumOfEvaluations();
	}

	/**
	 * Initialize the first population.
	 */
	@Override
	public void initPopulation(Config config, Population population) {
		for (int i = 0; i < config.getPopulationSize(); i++) {
			Chromozome newChromozome = createNewChromozome(config);
			if (newChromozome != null) {
				
				population.add(newChromozome);
			} else {

				i--;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * cz.vutbr.feec.utko.ef.evolution.EvolutionSpecifierAdapter#mutateAll(cz
	 * .vutbr.feec.utko.ef.core.Config, cz.vutbr.feec.utko.ef.core.Population)
	 */
	@Override
	public Population mutateAll(Config cfg, Population p) {
		log.debug("Begining mutateAll with population size:" + p.size());
		Population res = new Population();
		Random rnd = new Random();
		int success = 0;
		int improved = 0;
		int fail = 0;
		for (Chromozome i : p) {
			// CHANGED - the same problem as crossover has
			if (cfg.getMutationRate() > rnd.nextDouble()) {
				// if changed
				try {
					assert i.getCachedFitness() != null;
					double prevFittness = i.getCachedFitness();
					i.mutate();
					i.validate();
					i.setCachedFitness(evaluator.computeFitnessValue(i));
					if (prevFittness < i.getCachedFitness()) {
						res.add(i);
						improved++;
					}
					log.debug("Mutated with fitness " + prevFittness
							+ ", with improvement "
							+ (i.getCachedFitness() - prevFittness));
					success++;
				} catch (MutationIsNotPossibleException e) {
					fail++;
				}
			}
		}
		if (success > 0) {
			mutationSuccessRate = ((double) improved / (double) success);
		} else {
			mutationSuccessRate = 0.5;
		}

		assert !Double.isNaN(mutationSuccessRate);

		log.debug("Finishing mutateAll with population size:" + p.size()
				+ ", success: " + success + ", failed:" + fail + " improved:"
				+ mutationSuccessRate);
		return res;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * cz.vutbr.feec.utko.ef.evolution.EvolutionSpecifierAdapter#removeDuplicit
	 * (cz.vutbr.feec.utko.ef.core.Population)
	 */
	@Override
	public Population removeDuplicate(Population p) {
		// This implementation suppose that the exactly same fitness stands for
		// the same individuals !!!
		Double lastValue = null;
		Population res = new Population();
		for (Chromozome individual : p) {
			assert individual.getCachedFitness() != null;
			if (individual.getCachedFitness() != lastValue) {
				res.add(individual);
				lastValue = individual.getCachedFitness();
			}
		}
		return res;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @seecz.vutbr.feec.utko.ef.evolution.EvolutionSpecifierAdapter#
	 * sortAndSelectBestChromozomes(cz.vutbr.feec.utko.ef.core.Config,
	 * cz.vutbr.feec.utko.ef.core.Population)
	 */
	@Override
	public Population sortAndSelectBestChromozomes(Config cfg, Population p) {
		p.sortAccordingToFitness();
		
		double fitness = Double.MAX_VALUE;
		Toolkit.getDefaultToolkit().beep();     
		double bestFitness = Double.MAX_VALUE;
		for (Chromozome chromozome : p) {
			assert chromozome.getCachedFitness() != null : "Fitness must be defined here";
			assert fitness >= chromozome.getCachedFitness();
			assert bestFitness <= chromozome.getCachedFitness(); bestFitness = chromozome.getCachedFitness();
			fitness = chromozome.getCachedFitness();
		}
		
		p.cut(cfg.getPopulationSize());
		return p;
	}
}
